/*
	Programação Concorrente
	Trabalho 1
	Turma A
	
	Adilson Rapozo
	Luiz Bertoni
	Rodrigo Costa
*/

#include <stdio.h>
#include <gmp.h>
#include <pthread.h>
#include <semaphore.h>
#include <time.h>

#define N 23
#define PRECISION 33219281

typedef struct criticalRegionT {

	mpf_t a [ N ] ;
	sem_t semA [ N ] ;

	mpf_t b [ N ] ;
	sem_t semB [ N ] ;

	mpf_t t [ N ] ;
	sem_t semT [ N ] ;

	mpf_t p [ N ] ;
	sem_t semP [ N ] ;

} criticalRegionT ;

void * threadAF ( void * pArg ) {

	int i ;

	criticalRegionT * criticalRegion = ( criticalRegionT * ) pArg ;

	mpf_set_str ( criticalRegion -> a [ 0 ] , "1e0" , 10 ) ;

	sem_post ( & criticalRegion -> semA [ 0 ] ) ;

	for ( i = 1 ; i < N ; ++ i ) {

		sem_wait ( & criticalRegion -> semB [ i - 1 ] ) ;
		sem_post ( & criticalRegion -> semB [ i - 1 ] ) ;

		mpf_add ( criticalRegion -> a [ i ] , criticalRegion -> a [ i - 1 ] , criticalRegion -> b [ i - 1 ] ) ;
		mpf_div_ui ( criticalRegion -> a [ i ] , criticalRegion -> a [ i ] , 2 ) ;

		sem_post ( & criticalRegion -> semA [ i ] ) ;
	}

	return NULL ;
}

void * threadBF ( void * pArg ) {

	int i ;

	mpf_t sqrtTwo ;

	criticalRegionT * criticalRegion = ( criticalRegionT * ) pArg ;

	mpf_init ( sqrtTwo ) ;

	mpf_sqrt_ui ( sqrtTwo , 2 ) ;
	mpf_ui_div ( criticalRegion -> b [ 0 ] , 1 , sqrtTwo ) ;

	sem_post ( & criticalRegion -> semB [ 0 ] ) ;

	mpf_clear ( sqrtTwo ) ;

	for ( i = 1 ; i < N ; ++ i ) {

		sem_wait ( & criticalRegion -> semA [ i - 1 ] ) ;
		sem_post ( & criticalRegion -> semA [ i - 1 ] ) ;

		mpf_mul ( criticalRegion -> b [ i ] , criticalRegion -> a [ i - 1 ] , criticalRegion -> b [ i - 1 ] ) ;
		mpf_sqrt ( criticalRegion -> b [ i ] , criticalRegion -> b [ i ] ) ;

		sem_post ( & criticalRegion -> semB [ i ] ) ;
	}

	return NULL ;
}

void * threadTF ( void * pArg ) {

	int i ;

	criticalRegionT * criticalRegion = ( criticalRegionT * ) pArg ;

	mpf_set_str ( criticalRegion -> t [ 0 ] , "0.25e0" , 10 ) ;

	sem_post ( & ( criticalRegion -> semT [ 0 ] ) ) ;

	for ( i = 1 ; i < N ; ++ i ) {

		sem_wait ( & criticalRegion -> semA [ i ] ) ;
		sem_post ( & criticalRegion -> semA [ i ] ) ;

		sem_wait ( & criticalRegion -> semP [ i - 1 ] ) ;
		sem_post ( & criticalRegion -> semP [ i - 1 ] ) ;

		mpf_sub ( criticalRegion -> t [ i ] , criticalRegion -> a [ i - 1 ] , criticalRegion -> a [ i ] ) ;
		mpf_pow_ui ( criticalRegion -> t [ i ] , criticalRegion -> t [ i ] , 2 ) ;
		mpf_mul ( criticalRegion -> t [ i ] , criticalRegion -> t [ i ] , criticalRegion -> p [ i - 1 ] ) ;
		mpf_sub ( criticalRegion -> t [ i ] , criticalRegion -> t [ i - 1 ] , criticalRegion -> t [ i ] ) ;

		sem_post ( & ( criticalRegion -> semT [ i ] ) ) ;
	}

	return NULL ;
}

void * threadPF ( void * pArg ) {

	int i ;

	criticalRegionT * criticalRegion = ( criticalRegionT * ) pArg ;

	mpf_set_str (criticalRegion -> p [ 0 ] , "1e0" , 10 ) ;

	sem_post ( & criticalRegion -> semP [ 0 ] ) ;

	for ( i = 1 ; i < N ; ++ i ) {

		mpf_mul_ui ( criticalRegion -> p [ i ] , criticalRegion -> p [ i - 1 ] , 2 ) ;

		sem_post ( & criticalRegion -> semP [ i ] ) ;
	}

	return NULL ;
}

void * threadPiF ( void * pArg ) {

	int i ;

	criticalRegionT * criticalRegion = ( criticalRegionT * ) pArg ;

	FILE * file , file2 ;

	mpf_t pi ;
	mpf_t fourT ;

	mpf_init ( pi ) ;
	mpf_init ( fourT ) ;

	for ( i = 1 ; i < N ; ++ i ) {

		sem_wait ( & criticalRegion -> semA [ i ] ) ;
		sem_post ( & criticalRegion -> semA [ i ] ) ;

		sem_wait ( & criticalRegion -> semB [ i ] ) ;
		sem_post ( & criticalRegion -> semB [ i ] ) ;

		sem_wait ( & criticalRegion -> semT [ i ] ) ;
		sem_post ( & criticalRegion -> semT [ i ] ) ;

		mpf_add ( pi , criticalRegion -> a [ i ] , criticalRegion -> b [ i ] ) ;
		mpf_pow_ui ( pi , pi , 2 ) ;
		mpf_mul_ui ( fourT ,  criticalRegion -> t [ i ] , 4 ) ;
		mpf_div ( pi , pi , fourT ) ;

		/* saving result */

		file = savePI( "glpar" , pi, i ) ;
		file2 = fopen ( REAL_PI , "rw" ) ;

		printf ( "iteracao %d, %lu digitos corretos\n" , i , compare( file , file2 ) ) ;

		fclose ( file ) ;
		fclose ( file2 ) ;
	}

	mpf_clear ( pi ) ;
	mpf_clear ( fourT ) ;

	return NULL ;
}

int main ( void ) {

	int i ;

	clock_t start,end;

	criticalRegionT criticalRegion ;

	pthread_t threadA ;
	pthread_t threadB ;
	pthread_t threadT ;
	pthread_t threadP ;
	pthread_t threadPi ;

	/* initialization */

	mpf_set_default_prec ( PRECISION ) ;

	for ( i = 0 ; i < N ; ++ i ) {

		sem_init ( & criticalRegion . semA [ i ] , 0 , 0 ) ;
		sem_init ( & criticalRegion . semB [ i ] , 0 , 0 ) ;
		sem_init ( & criticalRegion . semT [ i ] , 0 , 0 ) ;
		sem_init ( & criticalRegion . semP [ i ] , 0 , 0 ) ;

		mpf_init ( criticalRegion . a [ i ] ) ;
		mpf_init ( criticalRegion . b [ i ] ) ;
		mpf_init ( criticalRegion . t [ i ] ) ;
		mpf_init ( criticalRegion . p [ i ] ) ;
	}

	start = clock();

	pthread_create ( & threadA , NULL , threadAF , ( void * ) ( & criticalRegion ) ) ;
	pthread_create ( & threadB , NULL , threadBF , ( void * ) ( & criticalRegion ) ) ;
	pthread_create ( & threadT , NULL , threadTF , ( void * ) ( & criticalRegion ) ) ;
	pthread_create ( & threadP , NULL , threadPF , ( void * ) ( & criticalRegion ) ) ;
	pthread_create ( & threadPi , NULL , threadPiF , ( void * ) ( & criticalRegion ) ) ;

	/* termination */

	pthread_join ( threadA , NULL ) ;
	pthread_join ( threadB , NULL ) ;
	pthread_join ( threadT , NULL ) ;
	pthread_join ( threadP , NULL ) ;
	pthread_join ( threadPi , NULL ) ;

	end = clock();

	printf("Executed in %.2lf seconds.\n",((double)(end-start))/CLOCKS_PER_SEC);

	for ( i = 0 ; i < N ; ++ i ) {

		sem_destroy ( & criticalRegion . semA [ i ] ) ;
		sem_destroy ( & criticalRegion . semB [ i ] ) ;
		sem_destroy ( & criticalRegion . semT [ i ] ) ;
		sem_destroy ( & criticalRegion . semP [ i ] ) ;

		mpf_clear ( criticalRegion . a [ i ] ) ;
		mpf_clear ( criticalRegion . b [ i ] ) ;
		mpf_clear ( criticalRegion . t [ i ] ) ;
		mpf_clear ( criticalRegion . p [ i ] ) ;
	}

	return 0 ;
}

